package com.hybris.msig.paymentgateway.controller;

import de.hybris.platform.converters.impl.AbstractPopulatingConverter;

import java.lang.reflect.Field;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.stream.Collectors;

import javax.annotation.Resource;
import javax.json.Json;
import javax.json.JsonObjectBuilder;
import javax.servlet.http.HttpServletRequest;

import org.apache.log4j.Logger;
import org.apache.velocity.util.StringUtils;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.client.RestTemplate;

import com.hybris.msig.paymentgateway.adapters.IPaymentGatewayAdapter;
import com.hybris.msig.paymentgateway.adapters.PaymentAdapterFactory;
import com.hybris.msig.paymentgateway.data.PaymentResponseData;
import com.hybris.msig.paymentgateway.dto.MsigPayRequestDto;
import com.hybris.msig.paymentgateway.dto.MsigPayRequestRefIdDto;
import com.hybris.msig.paymentgateway.dto.MsigPaymentCheckStatusDto;
import com.hybris.msig.paymentgateway.facade.MsigCommonPaymentFacade;


@Controller
public class PaymentGatewayController
{
	private static final Logger log = Logger.getLogger(PaymentGatewayController.class);

	@Resource
	protected PaymentAdapterFactory paymentAdapterFactory;

	@Resource
	protected MsigCommonPaymentFacade msigCommonPaymentFacade;

	@Resource
	protected AbstractPopulatingConverter<HttpServletRequest, PaymentResponseData> teleMoneyPaymentCallbackConverter;

	@RequestMapping(value = "/requestRefid")
	public @ResponseBody String requestRefid(final MsigPayRequestRefIdDto msigPayRequestRefIdDto)
	{
		System.out.println(msigPayRequestRefIdDto);
		final IPaymentGatewayAdapter adapter = paymentAdapterFactory.getPaymentGatewayAdapter(msigPayRequestRefIdDto.getGtw());
		return adapter.paymentRequestRefID(msigPayRequestRefIdDto);
	}

	@RequestMapping(value = "/requestPay")
	public String requestPay(final MsigPayRequestDto payRequestDto, final Model model)
	{
		System.out.println(payRequestDto);
		final IPaymentGatewayAdapter adapter = paymentAdapterFactory.getPaymentGatewayAdapter(payRequestDto.getGtw());
		model.addAllAttributes(adapter.build3rdPaymentRequest(payRequestDto));
		msigCommonPaymentFacade.initPaymentTransaction(payRequestDto);
		return adapter.paymentRequestPage();
	}

	//TODO: refactor these 3rd party return url to one
	@RequestMapping(value = "/telemoneyback")
	public String teleMoneyCallback(@RequestParam("portalCode")
	final String portalCode, @RequestParam("clientReturnUrl")
	final String clientReturnUrl, @RequestParam("refid")
	final String refid)
	{
		log.info("Tele Money Syn Back...");
		System.out.println("--------Tele money syn payment back--------[Portal Code] " + portalCode);
		System.out.println("--------Tele money syn payment back--------[clientReturnUrl] " + clientReturnUrl);
		System.out.println("--------Tele money syn payment back--------[refid] " + refid);

		//TODO: have to check and format the clientReturn parameters....
		//TODO: change to standard API
		final MsigPaymentCheckStatusDto checkStatusDto = new MsigPaymentCheckStatusDto();
		checkStatusDto.setRefid(refid);
		final PaymentResponseData paymentResponseData = msigCommonPaymentFacade.checkPaymentStatus(checkStatusDto);
		return "redirect:" + clientReturnUrl + "?gtw=TELE&portalCode=" + portalCode + "&status="
				+ paymentResponseData.getStatus();
	}

	@RequestMapping(value = "/eNetsback")
	public String eNetsCallback(@RequestParam("portalCode")
	final String portalCode, @RequestParam("clientReturnUrl")
	final String clientReturnUrl)
	{
		log.info("eNets Syn Back...");
		System.out.println("--------eNets syn payment back--------[Portal Code] " + portalCode);
		System.out.println("--------eNets syn payment back--------[clientReturnUrl] " + clientReturnUrl);

		//TODO: have to check and format the clientReturn parameters....
		//TODO: change to standard API
		return "redirect:" + clientReturnUrl + "?gtw=ENETS&portalCode=" + portalCode;
	}

	@RequestMapping(value = "/telemoneyAsyback")
	public @ResponseBody String teleMoneySynCallback(final HttpServletRequest request)
	{
		log.info("Tele Money Asyn Back...");
		log.info("-----attributes-----");
		Collections.list(request.getAttributeNames()).forEach(e -> log.info(e + " | " + request.getAttribute(e)));

		log.info("-----headers-----");
		Collections.list(request.getHeaderNames()).forEach(e -> log.info(e + " | " + request.getHeader(e)));

		log.info("-----request-----");
		request.getParameterMap()
				.forEach((k, v) -> log.info(k + "----" + Arrays.asList(v).stream().collect(Collectors.joining(" | "))));

		final String mid = request.getParameter("TM_MCode");
		final String ref = request.getParameter("TM_RefNo");
		final String status = request.getParameter("TM_Status");

		msigCommonPaymentFacade.updatePaymentTransaction(teleMoneyPaymentCallbackConverter.convert(request));
		final String back = "mid=" + mid + "&ref=" + ref + "&ack=" + status;
		log.info(new Date() + "write response... " + back);
		//		final IPaymentGatewayAdapter adapter = paymentAdapterFactory.getPaymentGatewayAdapter("TELE");
		//		if (adapter.paymentStatusPage() != null)
		//		{
		//			new Thread(() -> {
		//				final RestTemplate restTemplate = new RestTemplate();
		//				final MultiValueMap<String, Object> mm = new LinkedMultiValueMap<>();
		//				mm.add("mid", mid);
		//				mm.add("ref", ref);
		//				mm.add("status", status);
		//				final String s = restTemplate.postForObject(adapter.paymentStatusPage(), mm, String.class);
		//				log.info("Asyn invoke result from gateway : " + s);
		//			}).start();
		//		}
		return back;
	}

	@RequestMapping(value = "/eNetsAsyback")
	public @ResponseBody String eNetsSynCallback(final HttpServletRequest request)
	{
		log.info("eNets Asyn Back...");
		log.info("-----attributes-----");
		Collections.list(request.getAttributeNames()).forEach(e -> log.info(e + " | " + request.getAttribute(e)));

		log.info("-----headers-----");
		Collections.list(request.getHeaderNames()).forEach(e -> log.info(e + " | " + request.getHeader(e)));

		log.info("-----request-----");
		request.getParameterMap()
				.forEach((k, v) -> log.info(k + "----" + Arrays.asList(v).stream().collect(Collectors.joining(" | "))));

		final String mid = request.getParameter("TM_MCode");
		final String ref = request.getParameter("TM_RefNo");
		final String status = request.getParameter("TM_Status");

		final String back = "mid=" + mid + "&ref=" + ref + "&ack=" + status;
		log.info(new Date() + "write response... " + back);
		final IPaymentGatewayAdapter adapter = paymentAdapterFactory.getPaymentGatewayAdapter("ENETS");
		if (adapter.paymentStatusPage() != null)
		{
			new Thread(() -> {
				final RestTemplate restTemplate = new RestTemplate();
				final MultiValueMap<String, Object> mm = new LinkedMultiValueMap<>();
				mm.add("mid", mid);
				mm.add("ref", ref);
				mm.add("status", status);
				final String s = restTemplate.postForObject(adapter.paymentStatusPage(), mm, String.class);
				log.info("Asyn invoke result from gateway : " + s);
			}).start();
		}
		return back;
	}

	@RequestMapping(value = "/checkPaymentStatus")
	public @ResponseBody String checkPaymentStatus(final MsigPaymentCheckStatusDto msigPaymentCheckStatusDto)
	{
		return simpleObjectToMap(msigCommonPaymentFacade.checkPaymentStatus(msigPaymentCheckStatusDto));
	}

	/**
	 * Common method to change object to map<br/>
	 * Note: <b>This method is only for the objects whose attributes are all basic type, not used for the nested
	 * object!</b>
	 *
	 * @param obj
	 * @return json string for the map
	 */
	private String simpleObjectToMap(final Object obj)
	{
		final JsonObjectBuilder builder = Json.createBuilderFactory(null).createObjectBuilder();
		Arrays.asList(obj.getClass().getDeclaredFields()).stream()
				.collect(Collectors.<Field, String, Object> toMap(f -> f.getName(), f -> {
					final String methodName = "get" + StringUtils.capitalizeFirstLetter(f.getName());
					try
					{
						final Object result = obj.getClass().getMethod(methodName).invoke(obj);
						return result == null ? "" : result;
					}
					catch (final Exception e)
					{
						log.warn("reflection error." + e.getMessage());
						return "";
					}
				})).forEach((x, y) -> {
					builder.add(x, String.valueOf(y));
				});
		return builder.build().toString();
	}
}